package com.yqg.education.common.config;

import com.yqg.education.entity.LivePlayback;
import com.yqg.education.service.ILivePlaybackService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@ServerEndpoint(value = "/live/{deviceId}", configurator = SpringConfigurator.class)
@Component
public class WebSocketCameraServer {
    private static final Logger log = LoggerFactory.getLogger(WebSocketCameraServer.class);
    private static final Map<String, Session> ONLINE_CLIENTS = new ConcurrentHashMap<>();
    private static final Map<String, MediaRecorder> STREAMS = new ConcurrentHashMap<>();
    private static final Map<String, Long> livePlaybacks = new ConcurrentHashMap<>();

    private Session session;
    private String deviceId;
    private Process ffmpegProcess;
    private OutputStream ffmpegOutputStream;
    private OutputStream mp4FileOutputStream;
    @Value("${files.upload.path}")
    private String filePath;
    @Value("${ffmpeg.path}")
    private String ffmpegPath;
    private Path mp4FilePath;
    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    @Resource
    private ILivePlaybackService livePlaybackService;

    @OnOpen
    public void onOpen(Session session, @PathParam("deviceId") String deviceId) {
        this.session = session;
        this.deviceId = deviceId;
        ONLINE_CLIENTS.put(deviceId, session);

        try {
            // 创建保存目录
            Path saveDir = Paths.get(filePath);
            if (!Files.exists(saveDir)) {
                Files.createDirectories(saveDir);
            }

            // 生成MP4文件名
            String fileName = "video_" + deviceId + "_" + System.currentTimeMillis() + ".mp4";
            LivePlayback livePlayback = new LivePlayback();
            livePlayback.setLiveId(Long.valueOf(deviceId));
            livePlayback.setUrl("/api/file/download/" + filePath + fileName);
            livePlayback.setStartTime(LocalDateTime.now());
            livePlaybackService.save(livePlayback);
            livePlaybacks.put(deviceId, livePlayback.getId());
            mp4FilePath = saveDir.resolve(fileName);
            mp4FileOutputStream = Files.newOutputStream(mp4FilePath,
                    StandardOpenOption.CREATE,
                    StandardOpenOption.WRITE);

            // 更稳定的FFmpeg命令
            String[] command = {
                    ffmpegPath,
                    "-loglevel", "error",
                    "-f", "webm",      // 修改为webm格式，与输入数据匹配
                    "-i", "pipe:0",
                    "-c:v", "libx264",
                    "-preset", "ultrafast",
                    "-tune", "zerolatency",
                    "-c:a", "aac",
                    "-strict", "experimental",
                    "-b:a", "128k",
                    "-f", "flv",
                    "rtmp://172.16.1.228/hls/" + deviceId,
                    "-c", "copy",
                    "-f", "webm",
                    "pipe:1"
            };

            ProcessBuilder pb = new ProcessBuilder(command);
            pb.redirectErrorStream(true);
            ffmpegProcess = pb.start();
            isProcessing.set(true);

            // 监控FFmpeg进程状态
            new Thread(() -> {
                try {
                    int exitCode = ffmpegProcess.waitFor();
                    if (exitCode != 0) {
                        log.error("FFmpeg异常退出，代码: {}", exitCode);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    isProcessing.set(false);
                    cleanupResources();
                }
            }).start();

            // 读取FFmpeg输出
            new Thread(() -> {
                try (InputStream ffmpegOutput = ffmpegProcess.getInputStream()) {
                    byte[] buffer = new byte[8192];
                    int bytesRead;
                    while (isProcessing.get() && (bytesRead = ffmpegOutput.read(buffer)) != -1) {
                        mp4FileOutputStream.write(buffer, 0, bytesRead);
                    }
                } catch (Exception e) {
                    log.error("MP4文件写入错误", e);
                } finally {
                    closeQuietly(mp4FileOutputStream);
                }
            }).start();

            ffmpegOutputStream = ffmpegProcess.getOutputStream();
            log.info("FFmpeg进程启动成功，视频将保存到: {}", mp4FilePath);
        } catch (Exception e) {
            log.error("初始化失败", e);
            cleanupResources();
        }
    }

    @OnMessage
    public void onMessage(byte[] message, boolean isBinary) {
        if (!isProcessing.get() || ffmpegOutputStream == null) {
            log.warn("FFmpeg不可用，丢弃数据包");
            return;
        }

        try {
            ffmpegOutputStream.write(message);
            ffmpegOutputStream.flush();
        } catch (IOException e) {
            log.error("写入FFmpeg失败", e);
            cleanupResources();
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("摄像头{}发生错误", deviceId, error);
        cleanupResources();
    }

    @OnClose
    public void onClose(Session session, CloseReason reason) {
        String reasonMsg = reason != null ?
                (reason.getReasonPhrase() != null ? reason.getReasonPhrase() : "代码:" + reason.getCloseCode()) :
                "未知原因";
        log.info("摄像头{}正常关闭，原因: {}", deviceId, reasonMsg);
        cleanupResources();
    }

    private synchronized void cleanupResources() {
        try {
            // 关闭顺序很重要
            closeQuietly(ffmpegOutputStream);

            if (ffmpegProcess != null) {
                try {
                    ffmpegProcess.destroy();
                    if (!ffmpegProcess.waitFor(2, TimeUnit.SECONDS)) {
                        ffmpegProcess.destroyForcibly();
                    }
                } catch (Exception e) {
                    log.warn("终止FFmpeg进程异常", e);
                }
            }

            closeQuietly(mp4FileOutputStream);

            if (mp4FilePath != null && Files.exists(mp4FilePath)) {
                long fileSize = Files.size(mp4FilePath);
                log.info("视频文件已保存: {} ({} bytes)", mp4FilePath, fileSize);
            }
            LivePlayback livePlayback = livePlaybackService.getById(livePlaybacks.get(deviceId));
            livePlayback.setEndTime(LocalDateTime.now());
            livePlaybackService.saveOrUpdate(livePlayback);
        } catch (Exception e) {
            log.warn("资源清理异常", e);
        } finally {
            ONLINE_CLIENTS.remove(deviceId);
            STREAMS.remove(deviceId);
            isProcessing.set(false);
        }
    }

    private void closeQuietly(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                log.warn("关闭资源失败", e);
            }
        }
    }

    // 媒体录制器
    private static class MediaRecorder {
        private final String deviceId;
        private final Queue<byte[]> frameBuffer = new LinkedList<>();
        private volatile boolean isRunning = true;

        public MediaRecorder(String deviceId) {
            this.deviceId = deviceId;
            new Thread(this::forwardFrames).start();
        }

        private void forwardFrames() {
            while (isRunning) {
                try {
                    byte[] frame = frameBuffer.poll();
                    if (frame != null) {
                        // 广播逻辑...
                    }
                } catch (Exception e) {
                    isRunning = false;
                }
            }
        }

        public void writeFrame(byte[] frame) {
            frameBuffer.offer(frame);
            while (frameBuffer.size() > 10) frameBuffer.poll();
        }
    }
}
